﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RSCore.Helper.Collection;
using RSEngine.Math;

namespace RSEngine.Core
{

    //although the face class is fairly general
    // in what it can represent, many of the methods
    // assume triangles only
    sealed public class Face
    {

        #region Variables

        // For fast access
        public UnicityList<Vertex> Vertices = new UnicityList<Vertex>(3);
        public UnicityList<Edge> Edges = new UnicityList<Edge>(3);

        public Vector3 Normal = new Vector3();

        public int label;

        #endregion

        #region Constructor

        public Face()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Tell if the face is a boundary one.
        /// </summary>
        public bool IsBoundary
        {
            get
            {
                for (int index = 0; index < Edges.Count; index++)
                    if (Edges[index].IsBoundary)
                        return true;

                return false;
            }
        }

        #endregion

        #region Clone

        public Face Clone()
        {
            Face f = new Face();

            f.Edges = Edges.Clone();
            f.label = label;
            f.Normal = (Vector3)Normal.Clone();
            f.Vertices = Vertices.Clone();

            return f;
        }

        #endregion

        #region FindAdjacent

        /// <summary>
        /// Find the adjacent edge.
        /// </summary>
        public Edge FindAdjacent(Face face)
        {
            for (int i = 0; i < Edges.Count; i++)
                for (int j = 0; j < face.Edges.Count; j++)
                    if (Edges[i].Equals(face.Edges[j]))
                        return Edges[i];

            return null;
        }

        #endregion

        #region OffEdge

        public Vertex OffEdge(Edge e)
        {
            for (int i = 0; i < Vertices.Count; i++)
                if ((Vertices[i] != e.Vertices[0]) &&
                    (Vertices[i] != e.Vertices[1]))
                    return Vertices[i];

            return null;
        }

        #endregion

        #region Replace

        /// <summary>
        /// Replace an egde with another one.
        /// </summary>
        public void Replace(Edge oldEdge, Edge newEdge)
        {
            for (int i = 0; i < Edges.Count; i++)
                if (Edges[i].Equals(oldEdge))
                {
                    oldEdge.Faces.Remove(this);
                    Edges[i] = newEdge;
                    newEdge.Faces.Add(this);
                }
        }

        /// <summary>
        /// Replace a vertex with another one.
        /// </summary>
        public void Replace(Vertex oldVertex, Vertex newVertex)
        {
            for (int i = 0; i < Vertices.Count; i++)
                if (Vertices[i].Equals(oldVertex))
                {
                    oldVertex.Faces.Remove(this);
                    Vertices[i] = newVertex;
                    newVertex.Faces.Add(this);
                }
        }

        #endregion

        #region Disconnect

        public void Disconnect()
        {
            for (int i = 0; i < Vertices.Count; i++)
                Vertices[i].Faces.Remove(this);

            for (i = 0; i < Edges.Count; i++)
                Edges[i].Faces.Remove(this);

            Edges.Clear();
            Vertices.Clear();
        }

        #endregion

        #region UpdateNormal

        /// <summary>
        /// Update the normal.
        /// </summary>
        public void UpdateNormal()
        {
            Normal = (Vertices[1].Coordinate - Vertices[0].Coordinate).Cross(Vertices[2].Coordinate - Vertices[0].Coordinate);
            Normal.Normalize();
        }

        #endregion

        #region UpdateNormalTex

        /// <summary>
        /// Update the normal for the textures coordinates.
        /// </summary>
        public void UpdateNormalTex()
        {
            Normal = (Vertices[1].TexCoordinate - Vertices[0].TexCoordinate).Cross(Vertices[2].TexCoordinate - Vertices[0].TexCoordinate);
            Normal.Normalize();
        }

        #endregion

        #region Area

        public double Area()
        {
            Vector3 e1, e2;
            e1 = Vertices[1].Coordinate - Vertices[0].Coordinate;
            e2 = Vertices[2].Coordinate - Vertices[0].Coordinate;
            return System.Math.Abs(e1.Cross(e2).Length / 2);
        }

        #endregion

        #region TexArea

        public double TexArea()
        {
            Vector3 e1, e2;
            e1 = Vertices[1].TexCoordinate - Vertices[0].TexCoordinate;
            e2 = Vertices[2].TexCoordinate - Vertices[0].TexCoordinate;
            return System.Math.Abs(e1.Cross(e2).Length / 2);
        }

        #endregion

        #region Stretch

        //See: Texture mapping progressive meshes,
        // by Sander et al.
        public double Stretch()
        {
            Vector3 Ss, St;
            double a, b, c;
            double A;
            Vertex q1, q2, q3;
            q1 = Vertices[0];
            q2 = Vertices[1];
            q3 = Vertices[2];

            // x= s coordinate
            // y= t coordinate
            //Area of parameterised triangle
            A = ((q2.TexCoordinate.x - q1.TexCoordinate.x) * (q3.TexCoordinate.y - q1.TexCoordinate.y) -
               (q3.TexCoordinate.x - q1.TexCoordinate.x) * (q2.TexCoordinate.y - q1.TexCoordinate.y)) / 2.0;
            A = System.Math.Abs(A);
            if (A < 1e-10) A = 1e-10;
            Ss = q1.Coordinate * (q2.TexCoordinate.y - q3.TexCoordinate.y) +
                q2.Coordinate * (q3.TexCoordinate.y - q1.TexCoordinate.y) +
                q3.Coordinate * (q1.TexCoordinate.y - q2.TexCoordinate.y);
            St = q1.Coordinate * (q3.TexCoordinate.x - q2.TexCoordinate.x) +
                q2.Coordinate * (q1.TexCoordinate.x - q3.TexCoordinate.x) +
                q3.Coordinate * (q2.TexCoordinate.x - q1.TexCoordinate.x);
            Ss /= 2 * A;
            St /= 2 * A;
            a = Ss.Dot(Ss);
            b = Ss.Dot(St);
            c = St.Dot(St);

            //L^2 stretch metric from Sander et al.
            return System.Math.Sqrt((a + c) / 2.0);
        }

        #endregion

        #region Misc

        // neglects to check if vertices are in the
        // same order. But who wants a mesh where
        // the same vertices occur in several faces
        // but cross/overlap in some way?
        public override bool Equals(object obj)
        {
            int i, j, sum;
            int[] tag;
            Face f2;
            f2 = (Face)obj;
            tag = new int[Vertices.Count];
            for (i = 0; i < Vertices.Count; i++)
                tag[i] = 0;
            if (Vertices.Count != f2.Vertices.Count)
                return false;
            for (i = 0; i < Vertices.Count; i++)
            {
                for (j = 0; j < f2.Vertices.Count; j++)
                {
                    if (Vertices[i].Equals(f2.Vertices[j]))
                        tag[i] = 1;
                }
            }
            sum = 0;
            for (i = 0; i < Vertices.Count; i++)
                sum += tag[i];
            if (sum == Vertices.Count)
                return true;
            else return false;
        }

        #endregion

    }

}
